#include "downloadthreadmanager.h"
#include "downloadbreakpointconfigmanager.h"
#include "downloadstringutils.h"
#include "downloadurlencoder.h"
#include "downloadabstractnetwork.h"

static constexpr int THREADCOUNT = 1;

DownloadThreadManager::DownloadThreadManager(QObject *parent)
    : QObject(parent),
      m_runningCount(THREADCOUNT),
      m_file(nullptr),
      m_state(TTK::DownloadState::Stop)
{

}

DownloadThreadManager::~DownloadThreadManager()
{
    delete m_file;
    qDeleteAll(m_threads);
}

qint64 DownloadThreadManager::fileSize(QString &url, int tryTimes)
{
    qint64 size = -1;
    while(tryTimes--)
    {
        QNetworkAccessManager manager;

        QEventLoop loop;
        QNetworkRequest request;
        request.setUrl(url);
        TTK::setSslConfiguration(&request);

        QNetworkReply *reply = manager.head(request);
        if(!reply)
        {
            continue;
        }

        connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
        loop.exec();

        if(reply->error() != QNetworkReply::NoError)
        {
            continue;
        }

        size = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong();
        const QVariant &redirection = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
        if(!redirection.isNull())
        {
            url = redirection.toString();
            size = fileSize(url);
        }

        reply->deleteLater();
        break;
    }
    return size;
}

QString DownloadThreadManager::path() const
{
    return m_file ? m_file->fileName() : QString();
}

bool DownloadThreadManager::download(const QString &url, const QString &name)
{
    if(m_state == TTK::DownloadState::Download)
    {
        TTK_INFO_STREAM("Current is downloading a file");
        return true;
    }

    updateState(TTK::DownloadState::Wait);

    if(m_runningCount < 1 || 10 < m_runningCount)
    {
        TTK_INFO_STREAM("Download thread count error");
        updateState(TTK::DownloadState::Error);
        return false;
    }

    QString durl(DownloadUrlEncoder::decoder(url));
    if((m_totalSize = fileSize(durl)) == -1)
    {
        TTK_ERROR_STREAM("Download file size error");
        updateState(TTK::DownloadState::Error);
        return false;
    }

#if TTK_QT_VERSION_CHECK(5,2,0)
    QString fileName = QUrl(durl).fileName();
#else
    const QString &ourPath = QUrl(durl).path();
    const int slash = ourPath.lastIndexOf('/');
    QString fileName = (slash == -1) ? ourPath : ourPath.mid(slash + 1);
#endif
    if(name.isEmpty())
    {
        const QDir dir(TTK::String::downloadPrefix());
        QString prefixName = fileName;
        for(int i = 1; i < 99; ++i)
        {
            if(!dir.entryList().contains(prefixName))
            {
                break;
            }

            const int slash = fileName.lastIndexOf('.');
            prefixName = (slash == -1) ? fileName : fileName.left(slash);
            QString sufix = (slash == -1) ? fileName : fileName.mid(slash);

            if(prefixName == sufix)
            {
                sufix.clear();
            }

            prefixName = QString("%1(%2)%3").arg(prefixName).arg(i).arg(sufix);
        }
        fileName = prefixName;
    }
    else
    {
        fileName = name;
    }

    Q_EMIT updateFileInfoChanged(fileName, m_totalSize);
    fileName = TTK::String::downloadPrefix() + fileName;

    DownloadBreakPointConfigManager manager;
    DownloadBreakPointItemList records;
    if(manager.fromFile(fileName + STK_FILE))
    {
        manager.readBuffer(records);
    }

    m_readySize = 0;
    m_file = new QFile(fileName, this);

    if(!m_file->open(QIODevice::WriteOnly))
    {
        m_file->close();
        TTK_ERROR_STREAM("Can not open file : " + m_file->errorString());
        delete m_file;
        m_file = nullptr;

        updateState(TTK::DownloadState::Error);
        return false;
    }

    m_file->resize(m_totalSize);
    qDeleteAll(m_threads);
    m_threads.clear();

    for(int i = 0; i < THREADCOUNT; ++i)
    {
        qint64 startPoint = m_totalSize * i / THREADCOUNT;
        qint64 endPoint = m_totalSize * (i + 1) / THREADCOUNT;
        qint64 readySize = 0;

        if(!records.isEmpty())
        {
            const DownloadBreakPointItem &item = records[i];
            startPoint = !item.isEmpty() ? item.m_start : startPoint;
            endPoint = !item.isEmpty() ? item.m_end : endPoint;
            readySize = !item.isEmpty() ? item.m_ready : readySize;
        }

        DownloadThread *thread = new DownloadThread(this);
        connect(thread, SIGNAL(finished(int)), SLOT(finished(int)));
        connect(thread, SIGNAL(downloadChanged()), SLOT(progressChanged()));
        connect(thread, SIGNAL(errorOccurred(int,QString)), SLOT(handleError(int,QString)));
        thread->startDownload({i, durl, m_file, startPoint, endPoint, readySize});
        m_threads.append(thread);
    }

    updateState(TTK::DownloadState::Download);
    m_runningCount = THREADCOUNT;
    return true;
}

void DownloadThreadManager::updateState(TTK::DownloadState state)
{
    QString v;
    switch(m_state = state)
    {
    case TTK::DownloadState::Queue: v = tr("StateQueue"); break;
    case TTK::DownloadState::Wait: v = tr("StateWait"); break;
    case TTK::DownloadState::Download: v = tr("StateDownload"); break;
    case TTK::DownloadState::Pause: v = tr("StatePause"); break;
    case TTK::DownloadState::Stop: v = tr("StateStop"); break;
    case TTK::DownloadState::Finish: v = tr("StateFinish"); break;
    case TTK::DownloadState::Error: v = tr("StateError"); break;
    default: break;
    }

    Q_EMIT stateChanged(v);

    if(m_state == TTK::DownloadState::Error)
    {
        downloadingError();
    }
}

void DownloadThreadManager::downloadingFinish()
{
    const QString &fileName = m_file->fileName();
    m_file->flush();
    m_file->close();
    delete m_file;
    m_file = nullptr;
    updateState(TTK::DownloadState::Finish);

    QFile::remove(fileName + STK_FILE);

    qDeleteAll(m_threads);
    m_threads.clear();

    Q_EMIT downloadFinished(fileName);
}

void DownloadThreadManager::downloadingError()
{
    delete m_file;
    m_file = nullptr;

    Q_EMIT downloadFinished({});
}

void DownloadThreadManager::pause()
{
    if(m_state != TTK::DownloadState::Download && m_state != TTK::DownloadState::Wait)
    {
        TTK_ERROR_STREAM("Current is not downloading");
        return;
    }

    updateState(TTK::DownloadState::Pause);

    DownloadBreakPointItemList records;
    for(DownloadThread *thread : qAsConst(m_threads))
    {
        thread->pause();

        DownloadBreakPointItem item;
        item.m_url = thread->url();
        item.m_start = thread->startPoint();
        item.m_end = thread->endPoint();
        item.m_ready = thread->readySize();
        records << item;
    }

    if(m_file)
    {
        DownloadBreakPointConfigManager manager;
        if(manager.load(m_file->fileName() + STK_FILE))
        {
            manager.writeBuffer(records);
        }
    }
}

void DownloadThreadManager::restart()
{
    updateState(TTK::DownloadState::Download);

    for(DownloadThread *thread : qAsConst(m_threads))
    {
        thread->restart();
    }
}

void DownloadThreadManager::queue()
{
    updateState(TTK::DownloadState::Queue);
}

void DownloadThreadManager::finished(int index)
{
    m_runningCount--;
    TTK_INFO_STREAM("Download index of" << index << "finished");

    if(m_runningCount == 0 && m_state == TTK::DownloadState::Download)
    {
        downloadingFinish();
    }
}

void DownloadThreadManager::progressChanged()
{
    m_readySize = 0;
    for(DownloadThread *thread : qAsConst(m_threads))
    {
        m_readySize += thread->readySize();
    }

    Q_EMIT progressChanged(m_readySize, m_totalSize);
}

void DownloadThreadManager::handleError(int index, const QString &error)
{
    if(index < 0 || index >= m_threads.count())
    {
        return;
    }

    m_threads[index]->pause();
    TTK_INFO_STREAM("Download index of" << index << "error:" << error);
}
